#include "aos/cli.h"
#include <k_api.h>

typedef struct {
    char task_name[32];
    uint32_t task_id;
    uint32_t task_state;
    uint32_t stack_size;
    size_t free_size;
    uint64_t time_total;
    uint8_t task_prio;
    char candidate;
#if (RHINO_CONFIG_CPU_NUM > 1)
    uint8_t cpu_binded;
    uint8_t cpu_num;
    uint8_t cur_exc;
#endif
} dumpsys_task_info_t;

extern int cli_printf();
extern int32_t cli_register_commands(const struct cli_command *cmds, int32_t num);

/* convert int to ascii(HEX)
   while using format % in libc, malloc/free is involved.
   this function avoid using malloc/free. so it works when heap corrupt. */
char *k_int2str(int num, char *str) {
    char index[] = "0123456789ABCDEF";
    unsigned int usnum = (unsigned int)num;

    str[7] = index[usnum % 16];
    usnum /= 16;
    str[6] = index[usnum % 16];
    usnum /= 16;
    str[5] = index[usnum % 16];
    usnum /= 16;
    str[4] = index[usnum % 16];
    usnum /= 16;
    str[3] = index[usnum % 16];
    usnum /= 16;
    str[2] = index[usnum % 16];
    usnum /= 16;
    str[1] = index[usnum % 16];
    usnum /= 16;
    str[0] = index[usnum % 16];
    usnum /= 16;

    return str;
}

static void debug_task_show(ktask_t *task) {
    size_t free_size;
    int stat_idx;
    int i;
    int cpu_idx;
    char *cpu_stat[] = {"UNK",      "RDY", "PEND",    "SUS",
                        "PEND_SUS", "SLP", "SLP_SUS", "DEL"};
    const name_t *task_name;

    char s_task_overview[] = "                              0x         0x      "
                             "   0x        (0x        )\r\n";

    if (krhino_task_stack_min_free(task, &free_size) != RHINO_SUCCESS) {
        free_size = 0;
    }
    free_size *= sizeof(cpu_stack_t);

    /* set name */
    task_name = task->task_name == NULL ? "anonym" : task->task_name;
    for (i = 0; i < 20; i++) {
        s_task_overview[i] = ' ';
    }
    for (i = 0; i < strlen(task_name); i++) {
        s_task_overview[i] = task_name[i];
    }

    /* set state */
    stat_idx = task->task_state >= sizeof(cpu_stat) / sizeof(char *)
                   ? 0
                   : task->task_state;
    for (i = 21; i < 29; i++) {
        s_task_overview[i] = ' ';
    }
    for (i = 21; i < 29; i++) {
        if (cpu_stat[stat_idx][i - 21] == '\0') {
            break;
        }
        s_task_overview[i] = cpu_stat[stat_idx][i - 21];
    }

    /* set stack priority */
    k_int2str(task->prio, &s_task_overview[32]);

    /* set stack info */
    k_int2str((int)task->task_stack_base, &s_task_overview[43]);
    k_int2str((int)task->stack_size * sizeof(cpu_stack_t),
              &s_task_overview[54]);
    k_int2str((int)free_size, &s_task_overview[65]);
    cpu_idx = 65 + 17;

    (void)cpu_idx;
#if (RHINO_CONFIG_CPU_NUM > 1)
    s_task_overview[cpu_idx] = (uint8_t)('0' + task->cpu_binded);
    s_task_overview[cpu_idx + 10] = (uint8_t)('0' + task->cpu_num);
    s_task_overview[cpu_idx + 20] = (uint8_t)('0' + task->cur_exc);
#endif

    cli_printf(s_task_overview);
}

void debug_task_overview(char *buf, int len, int argc, char **argv) {
    klist_t *listnode;
    ktask_t *task;

    cli_printf("---------------------------------------------------------------"
               "-----------\r\n");
#if (RHINO_CONFIG_CPU_NUM > 1)
    cli_printf("TaskName             State    Prio       Stack      StackSize "
               "(MinFree)      cpu_binded    cpu_num    cur_exc\r\n");
#else
    cli_printf("TaskName             State    Prio       Stack      StackSize "
               "(MinFree)\r\n");
#endif
    cli_printf("---------------------------------------------------------------"
               "-----------\r\n");

    for (listnode = g_kobj_list.task_head.next;
         listnode != &g_kobj_list.task_head; listnode = listnode->next) {
        task = krhino_list_entry(listnode, ktask_t, task_stats_item);
        debug_task_show(task);
    }
}

uint32_t dumpsys_task_func() {
    kstat_t rst;
    size_t free_size = 0;
    uint64_t time_total = 0;
    /* consistent with "task_stat_t" */
    char *cpu_stat[] = {"ERROR",    "RDY", "PEND",    "SUS",
                        "PEND_SUS", "SLP", "SLP_SUS", "DELETED"};
    klist_t *taskhead = &g_kobj_list.task_head;
    klist_t *taskend = taskhead;
    klist_t *tmp;
    ktask_t *task;
    size_t corenum;
    (void)corenum;

#if (RHINO_CONFIG_CPU_NUM > 1)
    ktask_t *candidate[RHINO_CONFIG_CPU_NUM];
#else
    ktask_t *candidate;
#endif
    char yes = 'N';
    int32_t taskstate = 0;
    int32_t tasknum = 0;
    int32_t taskindex = 0;

    dumpsys_task_info_t *taskinfo;
    dumpsys_task_info_t *taskinfoeach;

    cli_printf("-----------------------------------------------------------"
               "---------------------\r\n");

    krhino_sched_disable();

#if (RHINO_CONFIG_CPU_NUM > 1)
    for (corenum = 0; corenum < RHINO_CONFIG_CPU_NUM; corenum++) {
        preferred_cpu_ready_task_get(&g_ready_queue, corenum);
        candidate[corenum] = g_preferred_ready_task[corenum];
    }

#else
    preferred_cpu_ready_task_get(&g_ready_queue, cpu_cur_get());
    candidate = g_preferred_ready_task[cpu_cur_get()];
#endif

    for (tmp = taskhead->next; tmp != taskend; tmp = tmp->next) {
        tasknum++;
    }

    taskinfo = krhino_mm_alloc(tasknum * sizeof(dumpsys_task_info_t));
    if (taskinfo == NULL) {
        krhino_sched_enable();
        return RHINO_NO_MEM;
    }
    memset(taskinfo, 0, tasknum * sizeof(dumpsys_task_info_t));

    for (tmp = taskhead->next; tmp != taskend; tmp = tmp->next) {

        char name_cut[19];
        taskinfoeach = taskinfo + taskindex;
        taskindex++;

        task = krhino_list_entry(tmp, ktask_t, task_stats_item);
        const name_t *task_name;
        rst = krhino_task_stack_min_free(task, &free_size);

        if (rst != RHINO_SUCCESS) {
            free_size = 0;
        }

#if (RHINO_CONFIG_SYS_STATS > 0)
        time_total = task->task_time_total_run / 20;
#endif

        if (task->task_name != NULL) {
            task_name = task->task_name;
        } else {
            task_name = "anonym";
        }
        strncpy(taskinfoeach->task_name, task_name,
                sizeof(taskinfoeach->task_name) - 1);
        taskinfoeach->task_id = task->task_id;

#if (RHINO_CONFIG_CPU_NUM > 1)
        for (corenum = 0; corenum < RHINO_CONFIG_CPU_NUM; corenum++) {
            if (candidate[corenum] == task) {
                yes = 'Y';
            } else {
                yes = 'N';
            }
        }

#else
        if (candidate == task) {
            yes = 'Y';
        } else {
            yes = 'N';
        }

#endif
        taskstate = task->task_state >= sizeof(cpu_stat) / sizeof(cpu_stat[0])
                        ? 0
                        : task->task_state;
        taskinfoeach->task_state = taskstate;
        taskinfoeach->task_prio = task->prio;
        taskinfoeach->stack_size = task->stack_size * sizeof(cpu_stack_t);
        taskinfoeach->free_size = free_size * sizeof(cpu_stack_t);
        taskinfoeach->time_total = time_total;
        taskinfoeach->candidate = yes;
#if (RHINO_CONFIG_CPU_NUM > 1)
        taskinfoeach->cpu_binded = task->cpu_binded;
        taskinfoeach->cpu_num = task->cpu_num;
        taskinfoeach->cur_exc = task->cur_exc;
#endif

        /* if not support %-N.Ms,cut it manually */
        if (strlen(task_name) > 18) {
            memset(name_cut, 0, sizeof(name_cut));
            memcpy(name_cut, task->task_name, 18);
            task_name = name_cut;

            strncpy(taskinfoeach->task_name, task_name, strlen(task_name));
        }
    }

    krhino_sched_enable();

#if (RHINO_CONFIG_CPU_NUM > 1)
    cli_printf("Name               ID    State    Prio StackSize MinFreesize "
               "Runtime  Candidate cpu_binded cpu_num cur_exc\r\n");
#else
    cli_printf("Name               ID    State    Prio StackSize MinFreesize "
               "Runtime  Candidate\r\n");
#endif

    cli_printf("-----------------------------------------------------------"
               "---------------------\r\n");

    for (taskindex = 0; taskindex < tasknum; taskindex++) {
        taskinfoeach = taskinfo + taskindex;

#if (RHINO_CONFIG_CPU_NUM > 1)
        cli_printf("%-19s%-6d%-9s%-5d%-10d%-12u%-9u   %-11c%-10d%-10d%-10d\r\n",
                   taskinfoeach->task_name, taskinfoeach->task_id,
                   cpu_stat[taskinfoeach->task_state], taskinfoeach->task_prio,
                   taskinfoeach->stack_size, taskinfoeach->free_size,
                   (uint32_t)taskinfoeach->time_total, taskinfoeach->candidate,
                   taskinfoeach->cpu_binded, taskinfoeach->cpu_num,
                   taskinfoeach->cur_exc);
#else
        cli_printf("%-19s%-6d%-9s%-5d%-10d%-12u%-9u   %-11c \r\n",
                   taskinfoeach->task_name, taskinfoeach->task_id,
                   cpu_stat[taskinfoeach->task_state], taskinfoeach->task_prio,
                   (int32_t)taskinfoeach->stack_size, taskinfoeach->free_size,
                   (uint32_t)taskinfoeach->time_total, taskinfoeach->candidate);
#endif
    }

    krhino_mm_free(taskinfo);
    cli_printf("-----------------------------------------------------------"
               "---------------------\r\n");

    return RHINO_SUCCESS;
}

static void task_cmd(char *buf, int len, int argc, char **argv) {
    dumpsys_task_func();
}

static const struct cli_command dumpsys_cli_cmd[] = {
    {"tasklist", "list all thread info", task_cmd},
    {"dumpsys", "dump system info", debug_task_overview},
};

int32_t dumpsys_cmds_register(void) {
    int32_t ret;

    ret = cli_register_commands(
        dumpsys_cli_cmd, sizeof(dumpsys_cli_cmd) / sizeof(struct cli_command));
    if (ret) {
        cli_printf("%s %d failed, ret = %d\r\n", __func__, __LINE__, ret);
    }
    return ret;
}